package cn.wolfcode.wolf2w.member.service.impl;

import cn.wolfcode.wolf2w.common.core.constant.SecurityConstants;
import cn.wolfcode.wolf2w.common.core.domain.R;
import cn.wolfcode.wolf2w.common.core.utils.StringUtils;
import cn.wolfcode.wolf2w.common.redis.service.RedisService;
import cn.wolfcode.wolf2w.common.redis.util.RedisKeys;
import cn.wolfcode.wolf2w.common.security.utils.SecurityUtils;
import cn.wolfcode.wolf2w.member.api.domain.UserFoandfan;
import cn.wolfcode.wolf2w.member.api.domain.UserInfo;
import cn.wolfcode.wolf2w.member.mapper.UserInfoMapper;
import cn.wolfcode.wolf2w.member.query.UserInfoQuery;
import cn.wolfcode.wolf2w.member.service.IUserFoandfanService;
import cn.wolfcode.wolf2w.member.service.IUserInfoService;
import cn.wolfcode.wolf2w.note.api.RemoteNoteService;
import cn.wolfcode.wolf2w.note.api.domain.Note;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;


/**
 * 前台用户Service业务层处理
 *
 * @author dafei
 * @date 2023-06-18
 */
@Service
@Transactional
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements IUserInfoService {
    @Autowired
    private RedisService redisService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private IUserFoandfanService userFoandfanService;
    @Autowired
    private RemoteNoteService remoteNoteService;

    @Override
    public IPage<UserInfo> queryPage(UserInfoQuery qo) {
        IPage<UserInfo> page = new Page<>(qo.getCurrentPage(), qo.getPageSize());
        return lambdaQuery()
                .like(StringUtils.hasText(qo.getNickname()), UserInfo::getNickname, qo.getNickname())
                .like(StringUtils.hasText(qo.getPhone()), UserInfo::getPhone, qo.getPhone())
                .eq(qo.getState() != null, UserInfo::getState, qo.getState())
                .page(page);
    }

    @Override
    public UserInfo queryByUsername(String username) {
        return lambdaQuery().eq(UserInfo::getPhone, username).one();
    }

    @Override
    public boolean checkPhoneUnique(UserInfo userInfo) {
        return lambdaQuery().eq(UserInfo::getPhone, userInfo.getPhone()).one() != null;
    }

    @Override
    public IPage<UserInfo> getFollow(UserInfoQuery qo) {
        IPage<UserInfo> page = new Page<>(qo.getCurrentPage(), qo.getPageSize());
        Long uid = SecurityUtils.getUserId();
        String key = RedisKeys.FOLLOWS_SET.join(uid.toString());

//        if (!redisService.hasKey(key)){
//            Set<Long> set = new HashSet<>();
//            set.add(-1L);
//            redisService.setCacheSet(key, set);
//        }

        Set<Long> set = redisService.getCacheSet(key);
        if (set != null && set.size() != 0) {
            List<Long> ids = set.stream().collect(Collectors.toList());
            page = lambdaQuery().in(UserInfo::getId, ids).page(page);
            List<UserInfo> list = page.getRecords();
            for (UserInfo u : list) {
                u.setFollowerTotal(redisService.getCacheSet(RedisKeys.FANS_SET.join(u.getId().toString())).size());
                R<List<Note>> noteRet = remoteNoteService.getByUid(u.getId(), SecurityConstants.INNER);
                if (noteRet != null && R.isSuccess(noteRet)) {
                    u.setTravelTotal(noteRet.getData().size());
                }
            }
            page.setRecords(list);
        }

        return page;
    }

    @Override
    public Boolean follow(Long id) {
        Long uid = SecurityUtils.getUserId();
        String fokey = RedisKeys.FOLLOWS_SET.join(uid.toString());
        String fankey = RedisKeys.FANS_SET.join(id.toString());

        boolean flag = false;
        if (redisService.isCacheSetContains(fokey, id)) {
            redisService.deleteCacheSetValue(fokey, id);
            if (redisService.isCacheSetContains(fankey, uid)) {
                redisService.deleteCacheSetValue(fankey, uid);
            }
        } else {
            redisService.addCacheSetValue(fokey, id);
            if (!redisService.isCacheSetContains(fankey, uid)) {
                redisService.addCacheSetValue(fankey, uid);
            }
            flag = true;
        }

        return flag;
    }

    @Override
    public IPage<UserInfo> getFans(UserInfoQuery qo) {
        IPage<UserInfo> page = new Page<>(qo.getCurrentPage(), qo.getPageSize());
        Long uid = SecurityUtils.getUserId();
        String key = RedisKeys.FANS_SET.join(uid.toString());

//        if (!redisService.hasKey(key)){
//            Set<Long> set = new HashSet<>();
//            set.add(-1L);
//            redisService.setCacheSet(key, set);
//        }

        Set<Long> set = redisService.getCacheSet(key);
        if (set != null && set.size() != 0) {
            List<Long> ids = set.stream().collect(Collectors.toList());
            page = lambdaQuery().in(UserInfo::getId, ids).page(page);
            List<UserInfo> list = page.getRecords();
            for (UserInfo u : list) {
               u.setFollowerTotal(redisService.getCacheSet(RedisKeys.FANS_SET.join(u.getId().toString())).size());
                R<List<Note>> noteRet = remoteNoteService.getByUid(u.getId(), SecurityConstants.INNER);
                if (noteRet != null && R.isSuccess(noteRet)) {
                    u.setTravelTotal(noteRet.getData().size());
                }
            }
            page.setRecords(list);
        }

        return page;
    }

    @Override
    public void updateCache() {
        List<String> fokeys = this.redisScan(RedisKeys.FOLLOWS_SET.join("*"));
        if (fokeys != null && fokeys.size() > 0) {
            for (String key : fokeys) {
                Long uid = Long.valueOf(key.split(":")[1]);
                String foids = redisService.getCacheSet(key)
                        .stream().map(String::valueOf)
                        .collect(Collectors.joining(","));
                List<UserFoandfan> list = userFoandfanService.lambdaQuery().eq(UserFoandfan::getUid, uid).list();
                if (list == null || list.size() == 0) {
                    UserFoandfan uf = new UserFoandfan();
                    uf.setUid(uid);
                    uf.setFoids(foids);
                    userFoandfanService.save(uf);
                } else {
                    userFoandfanService.lambdaUpdate()
                            .eq(UserFoandfan::getUid, uid)
                            .set(UserFoandfan::getFoids, foids)
                            .update();
                }
            }
        }

        List<String> fankeys = this.redisScan(RedisKeys.FANS_SET.join("*"));
        if (fankeys != null && fankeys.size() > 0) {
            for (String key : fankeys) {
                Long uid = Long.valueOf(key.split(":")[1]);
                String fanids = redisService.getCacheSet(key)
                        .stream().map(String::valueOf)
                        .collect(Collectors.joining(","));
                List<UserFoandfan> list = userFoandfanService.lambdaQuery().eq(UserFoandfan::getUid, uid).list();
                if (list == null || list.size() == 0) {
                    UserFoandfan uf = new UserFoandfan();
                    uf.setUid(uid);
                    uf.setFanids(fanids);
                    userFoandfanService.save(uf);
                } else {
                    userFoandfanService.lambdaUpdate()
                            .eq(UserFoandfan::getUid, uid)
                            .set(UserFoandfan::getFanids, fanids)
                            .update();
                }
            }
        }
    }

    /**
     * scan 实现
     *
     * @param pattern  表达式
     * @param consumer 对迭代到的key进行操作
     */
    public void scan(String pattern, Consumer<byte[]> consumer) {
        redisTemplate.execute((RedisConnection connection) -> {
            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) {
                cursor.forEachRemaining(consumer);
                return null;
            } catch (RuntimeException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 获取符合条件的key
     *
     * @param pattern 表达式
     * @return
     */
    public List<String> redisScan(String pattern) {
        List<String> keys = new ArrayList<>();
        this.scan(pattern, item -> {
            //符合条件的key
            String key = new String(item, StandardCharsets.UTF_8);
            keys.add(key);
        });
        return keys;
    }
}
